//
//  DownLoadTools.m
//  LearnImageCache
//
//  Created by isunn on 15/12/15.
//  Copyright (c) 2015年 Sunnsoft Co., Ltd. All rights reserved.
//

#import "DownLoadTools.h"
#import <UIKit/UIKit.h>

//存储的下载信息 {fileName:taskID}
#define DOWNLOAD_ATTRIBUTE_FILE @"DownLoadAttribute"

//下载的文件夹
#define DOCUMENTS_FILE_FOLDER @"CustomDownLoadPath"

//没有下载完的文件的后缀
#define UNFINISH_SUFFIX @"downloadTmp"

//下载完成的文件的后缀
#define FINISHED_SUFFIX @"dat"

//
#define TASKID_NOT_FONUD -1

NSString * const DownloadInfoNotification = @"DownloadInfoNotification";

@interface DownLoadTools ()

@property (nonatomic,strong) NSMutableDictionary * completeHandleDictionary;

@end

@implementation DownLoadTools

static DownLoadTools * tool = nil;

+(instancetype)shareTools
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        tool = [[DownLoadTools alloc]init];
        NSURLSessionConfiguration * cfg = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.klc"];
        tool.downSession = [NSURLSession sessionWithConfiguration:cfg delegate:tool delegateQueue:[NSOperationQueue mainQueue]];
        tool.taskInfos = [NSMutableDictionary dictionaryWithContentsOfFile:[tool downLoadAttributeFilePath]];
    });
    return tool;
}

#pragma mark - task tools

- (void)showLocalNotificationWithDownLoadTask:(NSURLSessionDownloadTask *)task
{
    UILocalNotification * localNoti = [[UILocalNotification alloc]init];
    [localNoti setFireDate:[NSDate dateWithTimeIntervalSinceNow:5]];
    localNoti.timeZone = [NSTimeZone defaultTimeZone];
    localNoti.soundName = UILocalNotificationDefaultSoundName;
    [localNoti setAlertTitle:@"下载完成 title"];
    localNoti.alertBody = [NSString stringWithFormat:@"%@ 已经下载完成",[self fileNameWithTaskId:task.taskIdentifier]];
    [[UIApplication sharedApplication] scheduleLocalNotification:localNoti];
}

- (void)addCompletionHandler:(CompletionHandlerType)handler forSession:(NSString *)identifier
{
    if ([self.completeHandleDictionary objectForKey: identifier]) {
        NSLog(@"Error: Got multiple handlers for a single session identifier.  This should not happen.\n");
    }
    [self.completeHandleDictionary setObject:handler forKey:identifier];
}

- (NSMutableDictionary *)taskInfos
{
    if (!_taskInfos) {
        _taskInfos =  [NSMutableDictionary dictionary];
    }
    return _taskInfos;
}

- (NSMutableDictionary *)completeHandleDictionary
{
    if (!_completeHandleDictionary) {
        _completeHandleDictionary = [NSMutableDictionary dictionary];
    }
    return _completeHandleDictionary;
}

//根据task 删除task id
- (void)taskInfoRemoveTask:(NSURLSessionDownloadTask *)task
{
    NSLog(@"%s",__FUNCTION__);
    NSString * fileName = [self fileNameWithTaskId:task.taskIdentifier];
    if (fileName) {
        [self taskInfoRemoveTaskWithFileName:fileName];
    }
}

//根据fileName 从taskInfo删除 task Id
- (void)taskInfoRemoveTaskWithFileName:(NSString *)fileName
{
    if (self.taskInfos.count) {
        if ([[self.taskInfos allKeys] containsObject:fileName]) {
            [self.taskInfos removeObjectForKey:fileName];
            NSLog(@"%s remove task info : %@",__FUNCTION__,fileName);
        }
        [self.taskInfos writeToFile:[self downLoadAttributeFilePath] atomically:YES];
    }
}

// 根据 fileName  获取task Id
- (NSInteger)downLoadTaskIdWithFileName:(NSString *)fileName
{
    if ([self.taskInfos objectForKey:fileName]) {
        return [[self.taskInfos objectForKey:fileName] integerValue];
    }
    NSLog(@"task id not found");
    return TASKID_NOT_FONUD;
}

// 根据task Id获取fileName
- (NSString *)fileNameWithTaskId:(NSInteger)taskId
{
//    NSLog(@"%s",__FUNCTION__);
    __block NSString * fileName = nil;
    [self.taskInfos enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        if ([obj integerValue] - taskId == 0) {
            fileName = (NSString *)key;
            *stop = YES;
        }
    }];
    if (!fileName) {
        NSLog(@"根据taskId:%ld 找不到fileName",taskId);
    }
    return fileName;
}

//查看download的一些详情
- (void)logDownLoadToolsInfo
{
    NSLog(@"task info count : %@",@(self.taskInfos.count));
    if (self.taskInfos.count) {
        [self.taskInfos enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            NSLog(@"key:%@ obj:%@",key,obj);
        }];
    }
    
    if (self.downSession) {
        [self.downSession getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
            NSLog(@"downSession task count : %@",@(downloadTasks.count));
            if (downloadTasks.count) {
                [downloadTasks enumerateObjectsUsingBlock:^(NSURLSessionDownloadTask * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                    NSLog(@"down load task id : %@",@(obj.taskIdentifier));
                }];
            }
        }];
    }
}


#pragma mark - task create
//普通的创建任务下载
- (NSUInteger )downLoadWithUrlString:(NSString *)downloadUrlString andfileName:(NSString *)fileName;
{
    NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:downloadUrlString]];
    NSURLSessionDownloadTask * downLoadTask = [self.downSession downloadTaskWithRequest:request];
    NSLog(@"file:%@ download task(id:%ld) has been created ",fileName,(NSInteger)(downLoadTask.taskIdentifier));
    [self.taskInfos setObject:@(downLoadTask.taskIdentifier) forKey:fileName];
    NSLog(@"task info : %@,%@",self.taskInfos,[self docPath]);
    [self.taskInfos writeToFile:[self downLoadAttributeFilePath] atomically:YES];
    [downLoadTask resume];
    return downLoadTask.taskIdentifier;
}

//续传的任务
- (void)bondDownloadedDataAndContinueDownload:(NSData *)resumeData andFileName:(NSString *)fileName
{
    NSURLSessionDownloadTask * resumeTask = [self.downSession downloadTaskWithResumeData:resumeData];
    NSLog(@"resumeTask has been created  tag : %ld",(NSInteger)(resumeTask.taskIdentifier));
    
    //重新创一个task后加入到本类的taskInfo信息中
    [self.taskInfos setObject:@(resumeTask.taskIdentifier) forKey:fileName];
    [self.taskInfos writeToFile:[self downLoadAttributeFilePath] atomically:YES];

    //resume的时候这个resumeData是被读取了，然后放在cache里了么？
    [resumeTask resume];
    
    //删除原有的数据任务
    [self deleteUnfinishFileWithFileName:fileName];
}

#pragma mark - task的状态
- (void)downloadFileStatusWithFileName:(NSString *)fileName andDownLoadStatusBlock:(void (^)(DownLoadStatus))statusBlock
{
   NSInteger taskID = [self downLoadTaskIdWithFileName:fileName];
    if (taskID == TASKID_NOT_FONUD) {
        //在session里没有记录就去目录里面找
        NSFileManager * manager = [[NSFileManager alloc]init];
        NSError * searchFileError = nil;
        NSArray * files = [manager contentsOfDirectoryAtPath:[self customDownloadDirect] error:&searchFileError];
        if ([files containsObject:[fileName stringByAppendingPathExtension:FINISHED_SUFFIX]]) {
            statusBlock(DownLoadStatusFinished);
        }
        else if([files containsObject:[fileName stringByAppendingPathExtension:UNFINISH_SUFFIX]]){
            statusBlock(DownLoadStatusCanceled);
        }
        else statusBlock(DownLoadStatusNotRecord);
    }
    else {
        [self.downSession getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
            for (NSURLSessionDownloadTask * downTask in downloadTasks) {
                if (downTask.taskIdentifier - taskID == 0) {
                    statusBlock((DownLoadStatus)(downTask.state));
                }
            }
        }];
    }
}

#pragma mark - 对session中的task进行操作

- (void)downLoadTaskHand:(DownLoadHandType)handType withFileName:(NSString *)fileName
{
    NSInteger taskId = [self downLoadTaskIdWithFileName:fileName];
    if (taskId == TASKID_NOT_FONUD) {  //表示当前没有task信息。
        NSLog(@"task id not found");
        if (handType == DownLoadHandContinue) {  // 可能是上次断掉了。需要经行重新创建task，其他的DownLoadHand就不管了
            NSLog(@"try to create task id with fileName according to location...");
            if ([self checkUnfinishedLoadHasExist:fileName]) {
                NSLog(@"unfinish file exist");
                [self bondDownloadedDataAndContinueDownloadWithFileName:fileName];
            }
            else {
                NSLog(@"unfinish file not exist . do u want reset download");
            }
        }
        return;
    }
    switch (handType) {
        case DownLoadHandCancel:
            [self downLoadTaskCancelWithID:taskId andfileName:fileName];
            break;
        case DownLoadHandContinue:
            [self downLoadTaskContinueWithID:taskId andfileName:fileName];
            break;
        case DownLoadHandPause:
            [self downLoadTaskPauseWithID:taskId andfileName:fileName];
            break;
        default:
            break;
    }
}

//暂停
- (void)downLoadTaskPauseWithID:(NSInteger)taskID andfileName:(NSString *)fileName
{
    [self.downSession getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        for (NSURLSessionDownloadTask * downTask in downloadTasks) {
            if (downTask.taskIdentifier - taskID == 0 && downTask.state == NSURLSessionTaskStateRunning) {
                NSLog(@"从downLoadTasks中取到task 暂停下载");
                [downTask suspend];
                NSLog(@"down task suspend \n countOfBytesExpectedToReceive:%lld \n countOfBytesExpectedToSend : %lld \n countOfBytesSent:%lld \n countOfBytesReceived:%lld",downTask.countOfBytesExpectedToReceive,downTask.countOfBytesExpectedToSend,downTask.countOfBytesSent,downTask.countOfBytesReceived);
            }
        }
    }];
}

//暂停后继续
- (void)downLoadTaskContinueWithID:(NSInteger)taskId andfileName:(NSString *)fileName
{
    [self.downSession getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        NSLog(@"data Tasks count : %ld",dataTasks.count);
        for (NSURLSessionDownloadTask * downTask in downloadTasks) {
            if (downTask.taskIdentifier - taskId == 0 && downTask.state != NSURLSessionTaskStateCompleted && downTask.state != NSURLSessionTaskStateRunning) {
                NSLog(@"从downLoadTasks中取到task 继续下载");
                [downTask resume];
            }
        }
    }];
}

//取消
/*
 Your app can obtain a resumeData object in two ways:
 If your app cancels an existing transfer by calling cancelByProducingResumeData:, the session object passes a resumeData object to the completion handler that you provided in that call.
 If a transfer fails, the session object provides an NSError object either to its delegate or to the task’s completion handler. In that object, the NSURLSessionDownloadTaskResumeData key in the userInfo dictionary contains a resumeData object.
 */

- (void)downLoadTaskCancelWithID:(NSInteger)taskId andfileName:(NSString *)fileName
{
    [self.downSession getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        for (NSURLSessionDownloadTask * downTask in downloadTasks) {
            if (downTask.taskIdentifier - taskId == 0 && downTask.state != NSURLSessionTaskStateCanceling) {
                NSLog(@"从downLoadTasks中取到task 取消下载");
                NSLog(@"task response : %@ \n orgin request : %@ \n ",downTask.response,downTask.originalRequest);
//                [downTask cancel];
                [downTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
                    //说是如果data以后可以被衔接上就会返回。貌似没看到。有时候又有。
                    if (resumeData) {
                        NSLog(@" resume data call back : %lu",resumeData.length);
                    }
                }];
            }
        }
    }];
}

//中断后继续
- (void)bondDownloadedDataAndContinueDownloadWithFileName:(NSString *)fileName
{
    NSString * currentFileName = [fileName stringByAppendingPathExtension:UNFINISH_SUFFIX];
    NSString * filePath = [self filePathWithFileName:currentFileName];
    NSData * resumeData = [NSData dataWithContentsOfFile:filePath];
    if (!resumeData) {
        NSLog(@"resume data not exit");
        return;
    }
    return [self bondDownloadedDataAndContinueDownload:resumeData andFileName:fileName];
}

//取消后继续
#pragma mark - session delegate

- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
{
    NSLog(@"URLSession didBecomeInvalidWithError : %@",error.localizedDescription);
}

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSLog(@"URLSession didReceiveChallenge");
}

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    NSLog(@"URLSession DidFinishEventsForBackgroundURLSession %ld",[UIApplication sharedApplication].applicationState);
    if (session.configuration.identifier) {
        CompletionHandlerType hander = [self.completeHandleDictionary objectForKey:session.configuration.identifier];
        if (hander) {
            [self.completeHandleDictionary removeObjectForKey:session.configuration.identifier];
            NSLog(@"Calling completion handler.\n");
            hander();
        }
    }
}


#pragma mark - session task delegate
//这个被手动kill后再次启动app 还是会触发这个方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    NSLog(@"%s",__FUNCTION__);
    if ([task isKindOfClass:[NSURLSessionDownloadTask class]]) {
        if(error){
            NSLog(@"fileName:%@ taskId : %@\n error : %@",[self fileNameWithTaskId:task.taskIdentifier],@(task.taskIdentifier),error.localizedDescription);
            NSDictionary * errorInfo = error.userInfo;
            NSData * resumeData = [errorInfo objectForKey:@"NSURLSessionDownloadTaskResumeData"];
            NSString * fileName = [self fileNameWithTaskId:task.taskIdentifier];
            if (resumeData) {
                if (!fileName) {  //如果取不到fileName
                    fileName = [NSString stringWithFormat:@"%@_taskID_%ld",[NSDate date],task.taskIdentifier];
                }
                NSError * resumeDataWriteError = nil;
                NSString * filePath = [self filePathWithFileName:[fileName stringByAppendingPathExtension:UNFINISH_SUFFIX]];
                if (filePath) {
                    [resumeData writeToFile:filePath options:NSAtomicWrite error:&resumeDataWriteError];
                    if(!resumeDataWriteError){
                        NSLog(@"文件保存成功 : %@ length:%lu \n hasReceived:%llu expectReceived:%llu  percent:%f",filePath,resumeData.length,task.countOfBytesReceived,task.countOfBytesExpectedToReceive,task.countOfBytesReceived * 1.0 / task.countOfBytesExpectedToReceive);
                    }
                    else {
                        NSLog(@"取消下载，文件保存失败 : %@", resumeDataWriteError.localizedDescription);
                    }
                }
                else NSLog(@"由于filepath 不存在不能写入数据");
            }
            else {
                NSLog(@"resume data 不能生成 请检查一下response");
            }
            //长时间暂停之后task 貌似会被session移除
        }
        else {
            NSLog(@" session did Complete success");
            [self showLocalNotificationWithDownLoadTask:(NSURLSessionDownloadTask *)task];
        }
        // 不管完没完成，移除这个task。
        [self taskInfoRemoveTask:(NSURLSessionDownloadTask *)task];
    }
}

#pragma mark - down load session delegate
//crash 以后还是会继续下载但是貌似不会被appdelegate调用handleEventsForBackgroundURLSession. (不考虑crash的情况)
// if app crashed and down load had finished. then run app again,it will invoke URLSession:downloadTask:didFinishDownloadingToURL method and then invoke URLSession:task:didCompleteWithError.last invoke URLSession DidFinishEventsForBackgroundURLSession.
// question how to save infomation if app crash? use file to save infomations when create task ?
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSLog(@"%s: %@ ",__FUNCTION__,location);
    NSFileManager * manager = [[NSFileManager alloc]init];
    NSError * moveError = nil;
    NSString * fileName = [self fileNameWithTaskId:downloadTask.taskIdentifier];
    NSString * filePath = [self filePathWithFileName:[fileName stringByAppendingPathExtension:FINISHED_SUFFIX]];
    NSURL * directPath = [NSURL fileURLWithPath:filePath isDirectory:YES];
    NSURL * unDirectPath = [NSURL fileURLWithPath:filePath isDirectory:NO];
    NSLog(@"\nlocation: %@ \ndirectPath : %@ \n unDirectPath :%@",location,directPath,unDirectPath);
    
    NSString * locationString = [[NSString stringWithFormat:@"%@",location] substringFromIndex:7];
    NSLog(@"locationString: %@",locationString);
    if ([manager fileExistsAtPath:locationString isDirectory:nil]) {
        NSLog(@"location string exsit");
    }
    else
        NSLog(@"location string not exsit");

    [manager moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath isDirectory:NO] error:&moveError];
    if (moveError) {
        NSLog(@"moveError : %@",moveError.localizedDescription);
    }
    else {
        NSLog(@"didFinishDownloadingToURL to new filePath : %@",filePath);
    }
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    CGFloat fileProgress = totalBytesWritten * 1.0 / totalBytesExpectedToWrite;
    NSLog(@"thread : %@ downLoad Id : %ld  progress : %.03f",[NSThread currentThread],downloadTask.taskIdentifier,fileProgress);
    NSString * fileName = [self fileNameWithTaskId:downloadTask.taskIdentifier];
//    NSNotification * noti = [NSNotification notificationWithName:DownloadInfoNotification object:@{@"fileName":fileName,@"taskID":@(downloadTask.taskIdentifier),@"progress":@(fileProgress),@"response":downloadTask.response}];
    NSNotification * noti = [NSNotification notificationWithName:DownloadInfoNotification object:@{@"fileName":fileName,@"taskID":@(downloadTask.taskIdentifier),@"progress":@(fileProgress)}];
    [[NSNotificationCenter defaultCenter]postNotification:noti];
}

#pragma mark - 路径有关的

//下载文件的属性plist路径
- (NSString *)downLoadAttributeFilePath
{
    return [[self docPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.plist",DOWNLOAD_ATTRIBUTE_FILE]];
}

//删除没有下载完的文件
- (void)deleteUnfinishFileWithFileName:(NSString *)fileName
{
    if ([self checkUnfinishedLoadHasExist:fileName]) {
        NSString * unfinishFileName = [fileName stringByAppendingPathExtension:UNFINISH_SUFFIX];
        NSFileManager * fileManager = [[NSFileManager alloc]init];
        NSError * deleteUnFinishError = nil;
        [fileManager removeItemAtPath:[[self customDownloadDirect] stringByAppendingPathComponent:unfinishFileName] error:&deleteUnFinishError];
        if (!deleteUnFinishError) {
            NSLog(@"%s %@ success",__FUNCTION__,fileName);
        }
        else NSLog(@"%s %@",__FUNCTION__,deleteUnFinishError.localizedDescription);
    }
}

//没有下载完的文件是否存在
- (BOOL)checkUnfinishedLoadHasExist:(NSString *)fileName
{
    NSString * unfinishFileName = [fileName stringByAppendingPathExtension:UNFINISH_SUFFIX];
    NSFileManager * fileManager = [[NSFileManager alloc]init];
    return [fileManager fileExistsAtPath:[[self customDownloadDirect] stringByAppendingPathComponent:unfinishFileName]];
}

- (NSString *)filePathWithFileName:(NSString *)fileName
{
    NSString * filePath = nil;
    filePath = [[self customDownloadDirect] stringByAppendingPathComponent:fileName];
    return  filePath;
}

- (NSString *)customDownloadDirect
{
    NSFileManager * fileManager = [[NSFileManager alloc]init];
    NSString * customDirect = [[self docPath] stringByAppendingPathComponent:DOCUMENTS_FILE_FOLDER];
    BOOL isExist = [fileManager fileExistsAtPath:customDirect isDirectory:nil];
    if (!isExist) {
        NSError * createDirectoryError = nil;
        [fileManager createDirectoryAtPath:customDirect withIntermediateDirectories:YES attributes:nil error:&createDirectoryError];
        if (createDirectoryError) {
            NSLog(@"createDirectoryError : %@",createDirectoryError.localizedDescription);
        }
    }
    return  customDirect;
}

- (NSString *)docPath
{
    NSString * docPath =  [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    return docPath;
}





@end
